/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/diagnostics_config.hpp>
#include <baja/trace.hpp>
#include <baja/callstack_fwd.hpp>

namespace baja
{
#ifdef BAJA_DEBUG

#if defined(BAJA_WIN32) && defined(_MSC_VER)
#define BAJA_DEBUG_BREAK __debugbreak();
#else // !BAJA_WIN32
#define BAJA_DEBUG_BREAK
#endif // BAJA_WIN32

#else
#define BAJA_DEBUG_BREAK
#endif

class baja_error : public std::logic_error
{
public:

    baja_error(const std::string& msg) : std::logic_error(msg), _gotStack(false) {}
    baja_error(const char* msg) : std::logic_error(msg), _gotStack(false) {}

    const std::string& stack() 
    {
        if (!_gotStack)
        {
            if (diagnostics::config::noStack)
                _stack = std::string("callstack not available");
            else
                _stack = getStackTrace(
                    2, // skip two frames
                    diagnostics::constants::totalStackFrames, // get a total of 10 frames
                    true // no throw
                    );

            _gotStack = true;
        }

        return _stack; 
    }

private:
    std::string _stack;
    bool _gotStack;
};

template <class Bogus> struct statics
{
	static std::function<void(const char*)> onExceptionThrown;
};

std::function<void(const char*)> statics<int>::onExceptionThrown;

inline void setOnExceptionThrown(std::function<void(const char*)> f)
{
	statics<int>::onExceptionThrown = f;
}

template <class Tex>
BAJA_INLINE void traceException(
    const Tex& e, 
    const char* file, 
    uint32 line,
    bool traceStack
#ifdef BAJA_DEBUG
    ,const char* etype
#endif
    )
{
    std::ostringstream sstm;
#ifdef BAJA_DEBUG
    if (etype) sstm << "details: " << etype << "\r\n";
#endif
    sstm << "what: " << e.what();
    if (file) sstm << "\r\nfile: " << file;
    if (line >= 0) sstm << "\r\nline:" << line;
    if (!diagnostics::config::noStack && traceStack)
    {
        sstm << "\r\nstack trace:" << 
            getStackTrace(
                2, // skip two frames
                diagnostics::constants::totalStackFrames,
                true // no throw
                );
    }

    BAJA_TRACE(sstm.str());
	
	if (statics<int>::onExceptionThrown)
	{
		statics<int>::onExceptionThrown(sstm.str().c_str());
	}
}

template <class Tex>
BAJA_INLINE void traceException(
    const Tex& e
#ifdef BAJA_DEBUG
    ,const char* etype
#endif
    )
{
    ::baja::traceException(e, nullptr, -1, false, etype);
}

template <class Tex>
BAJA_INLINE void bajaThrow(
    const Tex& e, 
    const char* file, 
    uint32 line
#ifdef BAJA_DEBUG
    ,const char* etype
#endif
    )
{
    BAJA_DEBUG_BREAK;

	BAJA_TRACE("--------------------------------------------------------------------------------");
    BAJA_TRACE("!! EXCEPTION THROWN");
    ::baja::traceException(
        e, 
        file, 
        line, 
        true  // trace callstack 
#ifdef BAJA_DEBUG
        ,etype
#endif
		);
    BAJA_TRACE("--------------------------------------------------------------------------------");

    throw e;
}

#ifdef BAJA_DEBUG
#define BAJA_TRACE_EXCEPTION(e) ::baja::traceException(e, __FILE__, __LINE__, false, nullptr);
#define BAJA_THROW(e) ::baja::bajaThrow(e, __FILE__, __LINE__, #e);
#else
#define BAJA_TRACE_EXCEPTION
#define BAJA_THROW(e) ::baja::bajaThrow(e, __FILE__, __LINE__);
#endif

#define BAJA_IGNORE_EXCEPTION(c_) \
    try { c_; } \
    catch (std::exception& e) \
    { \
        BAJA_TRACE("!!IGNORING EXCEPTION"); \
        BAJA_TRACE_EXCEPTION(e); \
    }

#define BAJA_CHECK_ARG(expr) \
{ \
    if (!(expr)) BAJA_THROW(std::invalid_argument(#expr)); \
}

}  // namespace baja

#ifdef BAJA_WIN32
#include <baja/error_win32.hpp>
#endif

#include <baja/callstack.hpp>
